I’m looking at games sales data - This is hopefully going to look
like a combination of total sales stats, trends by genre (if there is
that much detail), reviews(?)
Haven’t fully decided on the question I want to tackle, but I think
I’m approaching the scenario in a similar way to the brief
“Small games company want to understand what types of games sell a
lot of copies. In particular they are looking for analysis that helps
them decide which direction to take their company in.”
I’m not very happy with the dataset, as the landscape in 2019/16 was
vastly different to 2023.
VAMPIRE SURVIVORS DEFIES ALL LOGIC AND PREDICTIONS - PERHAPS
MANKIND WASNT MEANT TO KNOW WHAT GAMES WILL TAKE OFF
Currently i have a few datasets that might be useful for
supplementing the 2019 data from one of the example briefs
Hypothesis test - Franchise v non-Franchise
library(tidyverse)
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) :
invalid first argument
library(tidytext)
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) :
invalid first argument
library(ggwordcloud)
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) :
invalid first argument
library(janitor)
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) :
invalid first argument
steam <- read_csv("raw_data/steam.csv") # absolutely massive
Rows: 27075 Columns: 18── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (8): name, developer, publisher, platforms, categories, genres, steamspy_tags, owners
dbl (9): appid, english, required_age, achievements, positive_ratings, negative_ratings, average_playtime, median_playtime, price
date (1): release_date
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) :
invalid first argument
games_1 <- read_csv("raw_data/Video_Games.csv")
Rows: 16719 Columns: 16── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (8): Name, Platform, Year_of_Release, Genre, Publisher, User_Score, Developer, Rating
dbl (8): NA_Sales, EU_Sales, JP_Sales, Other_Sales, Global_Sales, Critic_Score, Critic_Count, User_Count
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) :
invalid first argument
Error in assign(cacheKey, frame, .rs.CachedDataEnv) :
attempt to use zero-length variable name
games_2 <- read_csv("raw_data/Video_Games_Sales.csv")
Rows: 16719 Columns: 16── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (8): Name, Platform, Year_of_Release, Genre, Publisher, User_Score, Developer, Rating
dbl (8): NA_Sales, EU_Sales, JP_Sales, Other_Sales, Global_Sales, Critic_Score, Critic_Count, User_Count
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
games_raw <- read_csv("raw_data/Raw Data GVGS&R.csv")
Rows: 16719 Columns: 16── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (8): Name, Platform, Year_of_Release, Genre, Publisher, User_Score, Developer, Rating
dbl (8): NA_Sales, EU_Sales, JP_Sales, Other_Sales, Global_Sales, Critic_Score, Critic_Count, User_Count
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
games_cleaned <- read_csv("raw_data/Cleaned Data 2 GVGS&R.csv")
Rows: 6894 Columns: 15── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (5): Name, Genre, Publisher, Developer, Rating
dbl (10): Year_of_Release, NA_Sales, EU_Sales, JP_Sales, Other_Sales, Global_Sales, Critic_Score, Critic_Count, User_Score, User_Count
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
games_raw %>%
arrange(Name) %>%
drop_na(Global_Sales) # No NA's in global sales - good start
steam_tags <- steam %>%
select(steamspy_tags) %>%
separate(col = steamspy_tags, sep = ";", into = c("tags", "tags_2", "tags_3")) %>%
pivot_longer(col = starts_with("tags"), values_to = "all_tags") %>%
select(all_tags) %>%
drop_na() %>%
count(all_tags)
Warning: Expected 3 pieces. Missing pieces filled with `NA` in 2665 rows [83, 95, 96, 98, 102, 103, 106, 119, 123, 124, 125, 161, 211, 212, 213, 224, 249, 250, 261, 262, ...].
ggwordcloud(words = steam_tags$all_tags, freq = steam_tags$n, random.color = TRUE, colors = c("#ff595e", "#ffca3a", "#8ac926", "#1982c4", "#6a4c93"))

steam %>%
arrange(desc(name))
games_backloggd <- read_csv("raw_data/games backloggd.csv")
New names:Rows: 1512 Columns: 14── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (12): Title, Release Date, Team, Times Listed, Number of Reviews, Genres, Summary, Reviews, Plays, Playing, Backlogs, Wishlist
dbl (2): ...1, Rating
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
Cleaning Backloggd data
games_backloggd <- games_backloggd %>%
clean_names()
# dropping unreleased games for now - only 3 of them, so won't impact anything too much
# converting release_date into a date and adding a years since release column - This could be usable to discount certain titles from a model
# e.g if there WAS a trend in games 10~ years ago, it may not be applicable today, and might produce incorrect guidance
# splitting genres into separate columns : only one game has 7 genre tags.
games_backloggd <- games_backloggd %>%
arrange(desc(rating)) %>%
filter(release_date != "releases on TBD") %>%
mutate(release_date = mdy(release_date),
time_since_release = as.period(today() - release_date),
years_since_release = as.numeric(time_since_release, "years"), .after = release_date) %>%
mutate(years_since_release = round(years_since_release, digits = 2)) %>%
select(!time_since_release) %>%
separate(col = genres, sep = "', '", into = c("genre_tag", "genre_tag_2", "genre_tag_3", "genre_tag_4", "genre_tag_5", "genre_tag_6", "genre_tag_7"))
Warning: Expected 7 pieces. Missing pieces filled with `NA` in 1508 rows [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...].
# removing the opening and closing brackets and apostrophes across the genre columns
games_backloggd <- games_backloggd %>%
mutate(genre_tag = str_replace_all(genre_tag, pattern = "\\['", replacement = ""),
across(starts_with("genre_tag"), ~ str_replace_all(., pattern = "'\\]", replacement = "")))
games_backloggd
NA
# Converted the below code into a function so i can reuse it
character_k_to_numeric <- function(x) {
x = enquo(x)
games_backloggd %>%
mutate(multiplier = case_when(
str_detect(!!x, pattern = "K$") ~ TRUE,
TRUE ~ FALSE)) %>%
mutate(!!x := str_remove(string = !!x, pattern = "K$")) %>%
mutate(!!x := as.numeric(!!x)) %>%
mutate(!!x := case_when(
multiplier == TRUE ~ !!x*1000,
TRUE ~ !!x
)) %>%
select(!multiplier)
}
# converts these columns into numerics
games_backloggd <- character_k_to_numeric(x = number_of_reviews)
games_backloggd <- character_k_to_numeric(x = plays)
games_backloggd <- character_k_to_numeric(x = wishlist)
games_backloggd <- games_backloggd %>%
select(!plays, !backlogs, !times_listed)
chunk below is original attempts at converting the above columns
# games_backloggd <- games_backloggd %>%
# select(!times_listed)
# mutating number of reviews to be numeric
# In this horrible step, I'm using str_detect and case_when to create a column called multiplier - this will be used later
# then i drop the "K"'s, and convert the column to a numeric
# then multiply the columns values by 1000 IF the multiplier column is TRUE
# games_backloggd %>%
# mutate(multiplier = case_when(
# str_detect(number_of_reviews, pattern = "K$") ~ TRUE,
# TRUE ~ FALSE
# ),.after = number_of_reviews) %>%
# mutate(number_of_reviews = str_remove(number_of_reviews, pattern = "K$")) %>%
# mutate(number_of_reviews = as.numeric(number_of_reviews)) %>%
# mutate(number_of_reviews = case_when(
# multiplier == TRUE ~ number_of_reviews*1000,
# TRUE ~ number_of_reviews
# )) %>%
# select(!multiplier)
#
# # Oh No i need to do this to more columns
#
# games_backloggd %>%
# mutate(multiplier = case_when(
# str_detect(plays, pattern = "K$") ~ TRUE,
# TRUE ~ FALSE
# ),.after = plays) %>%
# mutate(plays = str_remove(plays, pattern = "K$")) %>%
# mutate(plays = as.numeric(plays)) %>%
# mutate(plays = case_when(
# multiplier == TRUE ~ plays*1000,
# TRUE ~ plays
# )) %>%
# select(!multiplier)
# making it a function because that's easier
# character_k_to_numeric <- function(dataframe, column) {
# mutate(multiplier = case_when(
# str_detect(column, pattern = "K$") ~ TRUE,
# TRUE ~ FALSE
# ),.after = column) %>%
# mutate(column = str_remove(column, pattern = "K$")) %>%
# mutate(column = as.numeric(column)) %>%
# mutate(column = case_when(
# multiplier == TRUE ~ column*1000,
# TRUE ~ column
# )) %>%
# select(!multiplier)
# }
# tidying up team column
games_backloggd <- games_backloggd %>%
mutate(team = str_replace_all(team, pattern = "\\['", replacement = ""),
team = str_replace_all(team, pattern = "'\\]", replacement = ""),
team = str_replace_all(team, pattern = "\\', \\'", replacement = ", "))
# keeping these separate incase i need them
reviews_backloggd <- games_backloggd %>%
select(title, summary, reviews)
Happy with games_backlogged just now
write_csv(games_backloggd_clean, "clean_data/backloggd_clean")
Error in is.data.frame(x) : object 'games_backloggd_clean' not found
Looking at top games data
These seem to be the same thing
going with games_1 and renaming it something more sensible
HYPOTHESIS TEST: IN STEAM DATA, DOES HAVING SUPPORT FOR MULTIPLE
LANGUAGES AFFECT SALES? DUNNO MATE BUT I CAN CHECK I GUESS
# make platforms wider - windows_support = TRUE/FALSE, mac_support = TRUE/FALSE, linux_support = TRUE/FALSE
steam <- steam %>%
separate(col = platforms, sep = ";", into = c("platform_1", "platform_2", "platform_3"))
Warning: Expected 3 pieces. Missing pieces filled with `NA` in 21957 rows [21, 25, 27, 30, 34, 35, 36, 37, 38, 39, 40, 41, 42, 45, 46, 47, 48, 49, 50, 51, ...].
IN THIS VERSION OF THE STEAM DATASET, all but of the games listed as
having more than 20 million players are freemium? free to start? free to
play? (pick your poison) games
# Now the same but for multi-player - Local and Online
steam %>%
mutate(multiplayer = case_when(
str_detect(categories, "Local Multi-Player") & str_detect(categories, "Online Multi-Player") & str_detect(categories, "Co-op") ~ "Local + Online + Co-op",
str_detect(categories, "Local Multi-Player") & str_detect(categories, "Online Multi-Player") & !str_detect(categories, "Co-op") ~ "Local + Online",
str_detect(categories, "Local Multi-Player") & str_detect(categories, "Co-op") & !str_detect(categories, "Online Multi-Player") ~ "Local + Co-op",
str_detect(categories, "Online Multi-Player") & str_detect(categories, "Co-op") & !str_detect(categories, "Local Multi-Player") ~ "Online + Co-op",
str_detect(categories, "Local Multi-Player") & !str_detect(categories, "Online Multi-Player") ~ "Local only",
str_detect(categories, "Online Multi-Player") & !str_detect(categories, "Local Multi-Player") ~ "Online only",
str_detect(categories, "Multi-player") & str_detect(categories, "MMO") | str_detect(steamspy_tags, "MMO") ~ "Online only",
str_detect(categories, "Co-op") ~ "Co-op multiplayer",
str_detect(categories, "Multi-player") & !str_detect(categories, "Online Multi-Player") & !str_detect(categories, "Local Multi-Player") ~ "Multiplayer (Unspecified)",
TRUE ~ "No multiplayer"
),.after = virtual_reality_support)
# Could probably work it out in the case of the unspecified, but not worth going through case by case at the moment
LS0tDQp0aXRsZTogIkxvb2tpbmcgYXQgZGF0YSINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCkknbSBsb29raW5nIGF0IGdhbWVzIHNhbGVzIGRhdGEgLSBUaGlzIGlzIGhvcGVmdWxseSBnb2luZyB0byBsb29rIGxpa2UgYSBjb21iaW5hdGlvbiBvZiB0b3RhbCBzYWxlcyBzdGF0cywgdHJlbmRzIGJ5IGdlbnJlIChpZiB0aGVyZSBpcyB0aGF0IG11Y2ggZGV0YWlsKSwgcmV2aWV3cyg/KQ0KDQpIYXZlbid0IGZ1bGx5IGRlY2lkZWQgb24gdGhlIHF1ZXN0aW9uIEkgd2FudCB0byB0YWNrbGUsIGJ1dCBJIHRoaW5rIEknbSBhcHByb2FjaGluZyB0aGUgc2NlbmFyaW8gaW4gYSBzaW1pbGFyIHdheSB0byB0aGUgYnJpZWYNCg0KIlNtYWxsIGdhbWVzIGNvbXBhbnkgd2FudCB0byB1bmRlcnN0YW5kIHdoYXQgdHlwZXMgb2YgZ2FtZXMgc2VsbCBhIGxvdCBvZiBjb3BpZXMuIEluIHBhcnRpY3VsYXIgdGhleSBhcmUgbG9va2luZyBmb3IgYW5hbHlzaXMgdGhhdCBoZWxwcyB0aGVtIGRlY2lkZSB3aGljaCBkaXJlY3Rpb24gdG8gdGFrZSB0aGVpciBjb21wYW55IGluLiINCg0KSSdtIG5vdCB2ZXJ5IGhhcHB5IHdpdGggdGhlIGRhdGFzZXQsIGFzIHRoZSBsYW5kc2NhcGUgaW4gMjAxOS8xNiB3YXMgdmFzdGx5IGRpZmZlcmVudCB0byAyMDIzLiANCg0KDQojIyBJc3N1ZXMgaSdtIGV4cGVjdGluZyANCg0KLSBHYW1lcyBhcyBhIHNlcnZpY2UgYXMgYSBtb2RlbCBzdGlsbCBpc24ndCBkZWFkLCBzbyB0aGUgdHJhZGl0aW9uYWwgbWV0aG9kIG9mIGxvb2tpbmcgYXQgU2FsZXMgdG8ganVkZ2Ugc3VjY2VzcyBtaWdodCBub3QgYmUgZW5vdWdoDQoNCi0gU3Vic2NyaXB0aW9uIHNlcnZpY2VzIC0gY2hpZWZseSBHYW1lIFBhc3MsIGJ1dCBub3dhZGF5cyB5b3UgZG9uJ3QgcmVhbGx5IG5lZWQgdG8gYnV5IGEgZ2FtZSB5b3UncmUgaW50ZXJlc3RlZCBpbiAtIFNpbWlsYXIgbW9kZWwgdG8gbmV0ZmxpeA0KDQotIERMQyBhbmQgcG9zdC1sYXVuY2ggY29udGVudCAtIFlvdSBhcmVuJ3QgZmluaXNoZWQgYnV5aW5nIGEgZ2FtZSB3aGVuIHlvdSBwYXkgZm9yIGl0IGF0IGxhdW5jaC4gUGFpZCBjb250ZW50IGlzIGRyaXAgZmVkIGZvciB1cCB0byBzZXZlcmFsIHllYXJzIGFmdGVyIGEgZ2FtZXMgbGF1bmNoDQoNCi0gTWlzc2luZyBzYWxlcyBudW1iZXJzIC0gQXMgZmFyIGFzIEknbSBhd2FyZSwgYSBkZXZlbG9wZXIvcHVibGlzaGVyIGRvZXNuJ3QgaGF2ZSB0byByZWxlYXNlIHNhbGVzIG51bWJlcnMuDQoNCi0gTWlzc2luZyB0aXRsZXMgLSBJdCBtaWdodCBiZSBoYXJkIHRvIGJlbGlldmUsIGJ1dCB0aGVyZSBhcmUgbWFueSBtb3JlIGdhbWVzIG91dCB0aGVyZSB0aGFuIEZJRkEsIFNreXJpbSBhbmQgTWFyaW8gS2FydC4gU21hbGxlciBvciBtb3JlIG5pY2hlIHRpdGxlcyBtYXkgbm90IGJlIHJlcHJlc2VudGVkIF9fZXZlbiBpZiB0aGV5IGFyZSBhIGJldHRlciBtb2RlbCBmb3IgYSBzbWFsbGVyIGNvbXBhbnkgdG8gZW11bGF0ZV9fDQoNCi0gX19TdGVhbSBhbG9uZSBjb250YWlucyBvdmVyIDUwIHRob3VzYW5kIGdhbWVzX18gKGdyYW50ZWQsIG9mIGV4dHJlbWVseSB2YXJ5aW5nIHF1YWxpdHkpDQoNCi0gTWVhc3VyZXMgb2Ygc3VjY2VzcyAtIEl0IHNpbXBseSBpc24ndCBmYWlyIHRvIGp1ZGdlIHN1Y2Nlc3MgYmV0d2VlbiBzb21lIHRpdGxlcy4gQ29tcGFyaW5nIExldGhhbCBMZWFndWUgQmxhemUgdG8gU3VwZXIgU21hc2ggQnJvdGhlcnMgVWx0aW1hdGUgaXMgbm90DQphIGxldmVsIHBsYXlpbmcgZmllbGQuDQoNCiMjIEFzc3VtcHRpb25zDQoNCkJlZm9yZSBJIGV2ZW4gbG9vayBhdCB0aGUgZGF0YSwgSSdtIGV4cGVjdGluZyBzb21lIGJpYXMgLSBOZXdlciBjb25zb2xlcyBtaWdodCBub3QgYmUgcmVwcmVzZW50ZWQgaW4gZGF0YSwgd2hpY2ggbWlnaHQgInN1Z2dlc3QiIHRoYXQgaXQncyBiZXR0ZXIgdG8gZGV2ZWxvcCBhIHRpdGxlIGZvciBhbiBvbGRlciBzeXN0ZW0sIGFzIHRoZXkgaGF2ZSAibW9yZSBzdWNjZXNzIi4NCg0KU29tZSB0aXRsZXMgd2lsbCBibG93IG90aGVycyBvdXQgdGhlIHdhdGVyIGluIHRlcm1zIG9mIHNhbGVzLCBzaW1wbHkgZHVlIHRvIHRoZW0gYmVpbmcgbXVsdGktcGxhdGZvcm0uDQpGb3IgZXhhbXBsZSwgYSBzdWNjZXNzZnVsIFBTNCBleGNsdXNpdmUgdGl0bGUgd2lsbCBsaWtlbHkgbm90IHNlbGwgYXMgbWFueSBjb3BpZXMgYXMgYSBzdWNjZXNzZnVsIG11bHRpLXBsYXRmb3JtIHRpdGxlDQoNCk9uZSBvZiB0aGUgYmlnZ2VzdCBpbmZsdWVuY2VzIG9uIHNhbGVzIHdpbGwgYmUgVGl0bGUvRnJhbmNoaXNlIC0gSWYgYSBnYW1lIGlzIGNhbGxlZCAiUG9rZW1vbiIsIGNoYW5jZXMgYXJlIGl0J2xsIGRvIHdlbGwuIFRoaXMgaXMgbm90IHNvbWV0aGluZyB0aGF0IGEgc21hbGwgY29tcGFueSB3aWxsIGJlIGFibGUgdG8NCmVtdWxhdGUsIHVubGVzcyB0aGV5IGFyZSBsb29raW5nIHRvIGdldCBzdWVkLg0KDQojIF9fVkFNUElSRSBTVVJWSVZPUlMgREVGSUVTIEFMTCBMT0dJQyBBTkQgUFJFRElDVElPTlMgLSBQRVJIQVBTIE1BTktJTkQgV0FTTlQgTUVBTlQgVE8gS05PVyBXSEFUIEdBTUVTIFdJTEwgVEFLRSBPRkZfXw0KDQpDdXJyZW50bHkgaSBoYXZlIGEgZmV3IGRhdGFzZXRzIHRoYXQgbWlnaHQgYmUgdXNlZnVsIGZvciBzdXBwbGVtZW50aW5nIHRoZSAyMDE5IGRhdGEgZnJvbSBvbmUgb2YgdGhlIGV4YW1wbGUgYnJpZWZzDQoNCkh5cG90aGVzaXMgdGVzdCAtIEZyYW5jaGlzZSB2IG5vbi1GcmFuY2hpc2UNCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkodGlkeXRleHQpDQpsaWJyYXJ5KGdnd29yZGNsb3VkKQ0KbGlicmFyeShqYW5pdG9yKQ0KDQpzdGVhbSA8LSByZWFkX2NzdigicmF3X2RhdGEvc3RlYW0uY3N2IikgIyBhYnNvbHV0ZWx5IG1hc3NpdmUNCmdhbWVzXzEgPC0gcmVhZF9jc3YoInJhd19kYXRhL1ZpZGVvX0dhbWVzLmNzdiIpDQpnYW1lc18yIDwtIHJlYWRfY3N2KCJyYXdfZGF0YS9WaWRlb19HYW1lc19TYWxlcy5jc3YiKQ0KZ2FtZXNfcmF3IDwtIHJlYWRfY3N2KCJyYXdfZGF0YS9SYXcgRGF0YSBHVkdTJlIuY3N2IikNCmdhbWVzX2NsZWFuZWQgPC0gcmVhZF9jc3YoInJhd19kYXRhL0NsZWFuZWQgRGF0YSAyIEdWR1MmUi5jc3YiKQ0KYGBgDQpgYGB7cn0NCmdhbWVzX3JhdyAlPiUgDQogIGFycmFuZ2UoTmFtZSkgJT4lIA0KICBkcm9wX25hKEdsb2JhbF9TYWxlcykgIyBObyBOQSdzIGluIGdsb2JhbCBzYWxlcyAtIGdvb2Qgc3RhcnQNCmBgYA0KDQpgYGB7cn0NCg0KDQpgYGANCg0KYGBge3J9DQpzdGVhbV90YWdzIDwtIHN0ZWFtICU+JSANCiAgc2VsZWN0KHN0ZWFtc3B5X3RhZ3MpICU+JSANCiAgc2VwYXJhdGUoY29sID0gc3RlYW1zcHlfdGFncywgc2VwID0gIjsiLCBpbnRvID0gYygidGFncyIsICJ0YWdzXzIiLCAidGFnc18zIikpICU+JSANCiAgcGl2b3RfbG9uZ2VyKGNvbCA9IHN0YXJ0c193aXRoKCJ0YWdzIiksIHZhbHVlc190byA9ICJhbGxfdGFncyIpICU+JSANCiAgc2VsZWN0KGFsbF90YWdzKSAlPiUgDQogIGRyb3BfbmEoKSAlPiUgDQogIGNvdW50KGFsbF90YWdzKQ0KDQoNCmdnd29yZGNsb3VkKHdvcmRzID0gc3RlYW1fdGFncyRhbGxfdGFncywgZnJlcSA9IHN0ZWFtX3RhZ3MkbiwgcmFuZG9tLmNvbG9yID0gVFJVRSwgY29sb3JzID0gIGMoIiNmZjU5NWUiLCAiI2ZmY2EzYSIsICIjOGFjOTI2IiwgIiMxOTgyYzQiLCAiIzZhNGM5MyIpKQ0KDQpgYGANCg0KYGBge3J9DQoNCmBgYA0KDQoNCmBgYHtyfQ0Kc3RlYW0gJT4lIA0KICBhcnJhbmdlKGRlc2MobmFtZSkpDQpgYGANCg0KYGBge3J9DQpnYW1lc19iYWNrbG9nZ2QgPC0gcmVhZF9jc3YoInJhd19kYXRhL2dhbWVzIGJhY2tsb2dnZC5jc3YiKQ0KYGBgDQoNCiMjIENsZWFuaW5nIEJhY2tsb2dnZCBkYXRhDQoNCmBgYHtyfQ0KZ2FtZXNfYmFja2xvZ2dkIDwtIGdhbWVzX2JhY2tsb2dnZCAlPiUgDQogIGNsZWFuX25hbWVzKCkNCg0KIyBkcm9wcGluZyB1bnJlbGVhc2VkIGdhbWVzIGZvciBub3cgLSBvbmx5IDMgb2YgdGhlbSwgc28gd29uJ3QgaW1wYWN0IGFueXRoaW5nIHRvbyBtdWNoDQojIGNvbnZlcnRpbmcgcmVsZWFzZV9kYXRlIGludG8gYSBkYXRlIGFuZCBhZGRpbmcgYSB5ZWFycyBzaW5jZSByZWxlYXNlIGNvbHVtbiAtIFRoaXMgY291bGQgYmUgdXNhYmxlIHRvIGRpc2NvdW50IGNlcnRhaW4gdGl0bGVzIGZyb20gYSBtb2RlbA0KIyBlLmcgaWYgdGhlcmUgV0FTIGEgdHJlbmQgaW4gZ2FtZXMgMTB+IHllYXJzIGFnbywgaXQgbWF5IG5vdCBiZSBhcHBsaWNhYmxlIHRvZGF5LCBhbmQgbWlnaHQgcHJvZHVjZSBpbmNvcnJlY3QgZ3VpZGFuY2UNCiMgc3BsaXR0aW5nIGdlbnJlcyBpbnRvIHNlcGFyYXRlIGNvbHVtbnMgOiBvbmx5IG9uZSBnYW1lIGhhcyA3IGdlbnJlIHRhZ3MuIA0KZ2FtZXNfYmFja2xvZ2dkIDwtIGdhbWVzX2JhY2tsb2dnZCAlPiUgDQogIGFycmFuZ2UoZGVzYyhyYXRpbmcpKSAlPiUgDQogIGZpbHRlcihyZWxlYXNlX2RhdGUgIT0gInJlbGVhc2VzIG9uIFRCRCIpICU+JSANCiAgbXV0YXRlKHJlbGVhc2VfZGF0ZSA9IG1keShyZWxlYXNlX2RhdGUpLA0KICAgICAgICAgdGltZV9zaW5jZV9yZWxlYXNlID0gYXMucGVyaW9kKHRvZGF5KCkgLSByZWxlYXNlX2RhdGUpLA0KICAgICAgICAgeWVhcnNfc2luY2VfcmVsZWFzZSA9IGFzLm51bWVyaWModGltZV9zaW5jZV9yZWxlYXNlLCAieWVhcnMiKSwgLmFmdGVyID0gcmVsZWFzZV9kYXRlKSAlPiUgDQogIG11dGF0ZSh5ZWFyc19zaW5jZV9yZWxlYXNlID0gcm91bmQoeWVhcnNfc2luY2VfcmVsZWFzZSwgZGlnaXRzID0gMikpICU+JSANCiAgc2VsZWN0KCF0aW1lX3NpbmNlX3JlbGVhc2UpICU+JSANCiAgc2VwYXJhdGUoY29sID0gZ2VucmVzLCBzZXAgPSAiJywgJyIsIGludG8gPSBjKCJnZW5yZV90YWciLCAiZ2VucmVfdGFnXzIiLCAiZ2VucmVfdGFnXzMiLCAiZ2VucmVfdGFnXzQiLCAiZ2VucmVfdGFnXzUiLCAiZ2VucmVfdGFnXzYiLCAiZ2VucmVfdGFnXzciKSkNCg0KIyByZW1vdmluZyB0aGUgb3BlbmluZyBhbmQgY2xvc2luZyBicmFja2V0cyBhbmQgYXBvc3Ryb3BoZXMgYWNyb3NzIHRoZSBnZW5yZSBjb2x1bW5zDQpnYW1lc19iYWNrbG9nZ2QgPC0gZ2FtZXNfYmFja2xvZ2dkICU+JSANCiAgbXV0YXRlKGdlbnJlX3RhZyA9IHN0cl9yZXBsYWNlX2FsbChnZW5yZV90YWcsIHBhdHRlcm4gPSAiXFxbJyIsIHJlcGxhY2VtZW50ID0gIiIpLA0KICAgICAgICAgYWNyb3NzKHN0YXJ0c193aXRoKCJnZW5yZV90YWciKSwgfiBzdHJfcmVwbGFjZV9hbGwoLiwgcGF0dGVybiA9ICInXFxdIiwgcmVwbGFjZW1lbnQgPSAiIikpKSANCg0KZ2FtZXNfYmFja2xvZ2dkDQoNCmBgYA0KYGBge3J9DQojIENvbnZlcnRlZCB0aGUgYmVsb3cgY29kZSBpbnRvIGEgZnVuY3Rpb24gc28gaSBjYW4gcmV1c2UgaXQNCmNoYXJhY3Rlcl9rX3RvX251bWVyaWMgPC0gZnVuY3Rpb24oeCkgew0KICB4ID0gZW5xdW8oeCkNCiAgDQogIGdhbWVzX2JhY2tsb2dnZCAlPiUgDQogICAgIG11dGF0ZShtdWx0aXBsaWVyID0gY2FzZV93aGVuKA0KICAgIHN0cl9kZXRlY3QoISF4LCBwYXR0ZXJuID0gIkskIikgfiBUUlVFLA0KICAgIFRSVUUgfiBGQUxTRSkpICU+JSANCiAgbXV0YXRlKCEheCA6PSBzdHJfcmVtb3ZlKHN0cmluZyA9ICEheCwgcGF0dGVybiA9ICJLJCIpKSAlPiUgDQogIG11dGF0ZSghIXggOj0gYXMubnVtZXJpYyghIXgpKSAlPiUgDQogIG11dGF0ZSghIXggOj0gY2FzZV93aGVuKA0KICAgIG11bHRpcGxpZXIgPT0gVFJVRSB+ICEheCoxMDAwLA0KICAgIFRSVUUgfiAhIXgNCiAgKSkgJT4lIA0KICBzZWxlY3QoIW11bHRpcGxpZXIpDQp9DQoNCiMgY29udmVydHMgdGhlc2UgY29sdW1ucyBpbnRvIG51bWVyaWNzDQoNCmdhbWVzX2JhY2tsb2dnZCA8LSBjaGFyYWN0ZXJfa190b19udW1lcmljKHggPSBudW1iZXJfb2ZfcmV2aWV3cykNCg0KZ2FtZXNfYmFja2xvZ2dkIDwtIGNoYXJhY3Rlcl9rX3RvX251bWVyaWMoeCA9IHBsYXlzKQ0KDQpnYW1lc19iYWNrbG9nZ2QgPC0gY2hhcmFjdGVyX2tfdG9fbnVtZXJpYyh4ID0gd2lzaGxpc3QpDQpgYGANCg0KYGBge3J9DQpnYW1lc19iYWNrbG9nZ2QgPC0gZ2FtZXNfYmFja2xvZ2dkICU+JSANCiAgc2VsZWN0KCFwbGF5cywgIWJhY2tsb2dzLCAhdGltZXNfbGlzdGVkKQ0KYGBgDQoNCmNodW5rIGJlbG93IGlzIG9yaWdpbmFsIGF0dGVtcHRzIGF0IGNvbnZlcnRpbmcgdGhlIGFib3ZlIGNvbHVtbnMNCmBgYHtyfQ0KDQojIGdhbWVzX2JhY2tsb2dnZCA8LSBnYW1lc19iYWNrbG9nZ2QgJT4lIA0KIyAgIHNlbGVjdCghdGltZXNfbGlzdGVkKQ0KDQojIG11dGF0aW5nIG51bWJlciBvZiByZXZpZXdzIHRvIGJlIG51bWVyaWMNCiMgSW4gdGhpcyBob3JyaWJsZSBzdGVwLCBJJ20gdXNpbmcgc3RyX2RldGVjdCBhbmQgY2FzZV93aGVuIHRvIGNyZWF0ZSBhIGNvbHVtbiBjYWxsZWQgbXVsdGlwbGllciAtIHRoaXMgd2lsbCBiZSB1c2VkIGxhdGVyDQojIHRoZW4gaSBkcm9wIHRoZSAiSyIncywgYW5kIGNvbnZlcnQgdGhlIGNvbHVtbiB0byBhIG51bWVyaWMNCiMgdGhlbiBtdWx0aXBseSB0aGUgY29sdW1ucyB2YWx1ZXMgYnkgMTAwMCBJRiB0aGUgbXVsdGlwbGllciBjb2x1bW4gaXMgVFJVRQ0KIyBnYW1lc19iYWNrbG9nZ2QgJT4lDQojICAgbXV0YXRlKG11bHRpcGxpZXIgPSBjYXNlX3doZW4oDQojICAgICBzdHJfZGV0ZWN0KG51bWJlcl9vZl9yZXZpZXdzLCBwYXR0ZXJuID0gIkskIikgfiBUUlVFLA0KIyAgICAgVFJVRSB+IEZBTFNFDQojICAgKSwuYWZ0ZXIgPSBudW1iZXJfb2ZfcmV2aWV3cykgJT4lDQojICAgbXV0YXRlKG51bWJlcl9vZl9yZXZpZXdzID0gc3RyX3JlbW92ZShudW1iZXJfb2ZfcmV2aWV3cywgcGF0dGVybiA9ICJLJCIpKSAlPiUNCiMgICBtdXRhdGUobnVtYmVyX29mX3Jldmlld3MgPSBhcy5udW1lcmljKG51bWJlcl9vZl9yZXZpZXdzKSkgJT4lDQojICAgbXV0YXRlKG51bWJlcl9vZl9yZXZpZXdzID0gY2FzZV93aGVuKA0KIyAgICAgbXVsdGlwbGllciA9PSBUUlVFIH4gbnVtYmVyX29mX3Jldmlld3MqMTAwMCwNCiMgICAgIFRSVUUgfiBudW1iZXJfb2ZfcmV2aWV3cw0KIyAgICkpICU+JQ0KIyAgIHNlbGVjdCghbXVsdGlwbGllcikNCiMgDQojICMgT2ggTm8gaSBuZWVkIHRvIGRvIHRoaXMgdG8gbW9yZSBjb2x1bW5zDQojIA0KIyBnYW1lc19iYWNrbG9nZ2QgJT4lIA0KIyAgICBtdXRhdGUobXVsdGlwbGllciA9IGNhc2Vfd2hlbigNCiMgICAgIHN0cl9kZXRlY3QocGxheXMsIHBhdHRlcm4gPSAiSyQiKSB+IFRSVUUsDQojICAgICBUUlVFIH4gRkFMU0UNCiMgICApLC5hZnRlciA9IHBsYXlzKSAlPiUgDQojICAgbXV0YXRlKHBsYXlzID0gc3RyX3JlbW92ZShwbGF5cywgcGF0dGVybiA9ICJLJCIpKSAlPiUgDQojICAgbXV0YXRlKHBsYXlzID0gYXMubnVtZXJpYyhwbGF5cykpICU+JSANCiMgICBtdXRhdGUocGxheXMgPSBjYXNlX3doZW4oDQojICAgICBtdWx0aXBsaWVyID09IFRSVUUgfiBwbGF5cyoxMDAwLA0KIyAgICAgVFJVRSB+IHBsYXlzDQojICAgKSkgJT4lIA0KIyAgIHNlbGVjdCghbXVsdGlwbGllcikNCg0KIyBtYWtpbmcgaXQgYSBmdW5jdGlvbiBiZWNhdXNlIHRoYXQncyBlYXNpZXINCiMgY2hhcmFjdGVyX2tfdG9fbnVtZXJpYyA8LSBmdW5jdGlvbihkYXRhZnJhbWUsIGNvbHVtbikgew0KIyAgICAgIG11dGF0ZShtdWx0aXBsaWVyID0gY2FzZV93aGVuKA0KIyAgICAgc3RyX2RldGVjdChjb2x1bW4sIHBhdHRlcm4gPSAiSyQiKSB+IFRSVUUsDQojICAgICBUUlVFIH4gRkFMU0UNCiMgICApLC5hZnRlciA9IGNvbHVtbikgJT4lIA0KIyAgIG11dGF0ZShjb2x1bW4gPSBzdHJfcmVtb3ZlKGNvbHVtbiwgcGF0dGVybiA9ICJLJCIpKSAlPiUgDQojICAgbXV0YXRlKGNvbHVtbiA9IGFzLm51bWVyaWMoY29sdW1uKSkgJT4lIA0KIyAgIG11dGF0ZShjb2x1bW4gPSBjYXNlX3doZW4oDQojICAgICBtdWx0aXBsaWVyID09IFRSVUUgfiBjb2x1bW4qMTAwMCwNCiMgICAgIFRSVUUgfiBjb2x1bW4NCiMgICApKSAlPiUgDQojICAgc2VsZWN0KCFtdWx0aXBsaWVyKQ0KIyB9DQoNCmBgYA0KDQoNCmBgYHtyfQ0KIyB0aWR5aW5nIHVwIHRlYW0gY29sdW1uDQpnYW1lc19iYWNrbG9nZ2QgPC0gZ2FtZXNfYmFja2xvZ2dkICU+JSANCiAgbXV0YXRlKHRlYW0gPSBzdHJfcmVwbGFjZV9hbGwodGVhbSwgcGF0dGVybiA9ICJcXFsnIiwgcmVwbGFjZW1lbnQgPSAiIiksDQogICAgICAgICB0ZWFtID0gc3RyX3JlcGxhY2VfYWxsKHRlYW0sIHBhdHRlcm4gPSAiJ1xcXSIsIHJlcGxhY2VtZW50ID0gIiIpLA0KICAgICAgICAgdGVhbSA9IHN0cl9yZXBsYWNlX2FsbCh0ZWFtLCBwYXR0ZXJuID0gIlxcJywgXFwnIiwgcmVwbGFjZW1lbnQgPSAiLCAiKSkgDQpgYGANCg0KYGBge3J9DQojIGtlZXBpbmcgdGhlc2Ugc2VwYXJhdGUgaW5jYXNlIGkgbmVlZCB0aGVtDQpyZXZpZXdzX2JhY2tsb2dnZCA8LSBnYW1lc19iYWNrbG9nZ2QgJT4lIA0KICBzZWxlY3QodGl0bGUsIHN1bW1hcnksIHJldmlld3MpIA0KYGBgDQoNCmBgYHtyfQ0KIyBjdXR0aW5nIHRoZXNlIGNvbHVtbnMgZm9yIG5vdyAtIGlmIG5lZWRlZCwgaSdsbCByZWF0dGFjaCB0aGVtDQpnYW1lc19iYWNrbG9nZ2QgPC0gZ2FtZXNfYmFja2xvZ2dkICU+JSANCiAgc2VsZWN0KCF4MSkNCmBgYA0KDQpgYGB7cn0NCmdhbWVzX2JhY2tsb2dnZCA8LSBnYW1lc19iYWNrbG9nZ2QgJT4lIA0KICBzZWxlY3QoLXN1bW1hcnksIC10aW1lc19saXN0ZWQsIC1yZXZpZXdzLCAtcGxheWluZywgLWJhY2tsb2dzKSANCmBgYA0KDQpgYGB7cn0NCiMgcmVtb3ZpbmcgZHVwbGljYXRlZCByb3dzDQoNCmdhbWVzX2JhY2tsb2dnZCA8LSBnYW1lc19iYWNrbG9nZ2QgJT4lIA0KICB1bmlxdWUoKQ0KYGBgDQoNCiMjIEhhcHB5IHdpdGggZ2FtZXNfYmFja2xvZ2dlZCBqdXN0IG5vdw0KDQpgYGB7cn0NCmdhbWVzX2JhY2tsb2dnZF9jbGVhbiA8LSBnYW1lc19iYWNrbG9nZ2QNCndyaXRlX2NzdihnYW1lc19iYWNrbG9nZ2RfY2xlYW4sICJjbGVhbl9kYXRhL2JhY2tsb2dnZF9jbGVhbi5jc3YiKQ0KYGBgDQoNCiMjIExvb2tpbmcgYXQgdG9wIGdhbWVzIGRhdGENCg0KYGBge3J9DQpnYW1lc18xIA0KYGBgDQoNCmBgYHtyfQ0KZ2FtZXNfMg0KYGBgDQpUaGVzZSBzZWVtIHRvIGJlIHRoZSBzYW1lIHRoaW5nDQoNCmdvaW5nIHdpdGggZ2FtZXNfMSBhbmQgcmVuYW1pbmcgaXQgc29tZXRoaW5nIG1vcmUgc2Vuc2libGUNCg0KYGBge3J9DQpnYW1lc19zYWxlcyA8LSBnYW1lc18xDQpgYGANCg0KYGBge3J9DQpnYW1lc19zYWxlcyA8LSBnYW1lc19zYWxlcyAlPiUgDQogIGNsZWFuX25hbWVzKCkgDQpgYGANCg0KYGBge3J9DQpnYW1lc19zYWxlcyAlPiUgDQogIGZpbHRlcihuYW1lID09ICJNaW5lY3JhZnQiKSAlPiUgDQogIGNvdW50KHN1bShnbG9iYWxfc2FsZXMpKQ0KYGBgDQoNCmBgYHtyfQ0KZ2FtZXNfc2FsZXMgJT4lIA0KICBncm91cF9ieShuYW1lKSAlPiUgDQogIA0KYGBgDQojIG1pZ2h0IGhhdmUgdG8gZmFmZiBhcm91bmQgdG8gZ2V0IGFuIGFjY3VyYXRlIHBpY3R1cmUgb2Ygc2FsZXMgZm9yIHRoaXMgb25lDQoNCmBgYHtyfQ0KdmdfY2hhcnR6X2ZlYjIzIDwtIHJlYWRfY3N2KCJyYXdfZGF0YS9nYW1lX3N0YXRpc3RpY3NfZmViXzIwMjMuY3N2IikNCmBgYA0KDQpgYGB7cn0NCnZnX2NoYXJ0el9mZWIyMw0KYGBgDQpgYGB7cn0NCiMgZG9udCByZWFsbHkgbmVlZCB0byBrbm93IHdoZXJlIHRoZXNlIGdhbWVzIGZhbGwgb24gdGhlIGNoYXJ0cyBmb3IgdGhpcyBzaXRlIC0gbm90ZTogcG9zIG1lYW5zIHBvc2l0aW9uLiBOb3QgYW55dGhpbmcgZWxzZS4NCnZnX2NoYXJ0el9mZWIyMyA8LSB2Z19jaGFydHpfZmViMjMgJT4lIA0KICBzZWxlY3QoLXBvcykNCmBgYA0KDQpQbGF0Zm9ybSBpcyBhIGxpdHRsZSB1bmhlbHBmdWwuICJBbGwiIGRvZXMgbm90IG1lYW4gYWxsIHBsYXRmb3JtcyAodW5sZXNzIEVsZGVuIFJpbmcgcmVsZWFzZWQgb24gU3dpdGNoLCBvciB0aGUgTkVTKQ0KIlNlcmllcyIgbWlnaHQgYmUgdXNlZnVsIGFzIGEgbG9vay11cCBmb3IgYW5vdGhlciB0YWJsZSB0aG91Z2gNCg0KYGBge3J9DQpsaXN0X29mX2ZyYW5jaGlzZXMgPC0gdmdfY2hhcnR6X2ZlYjIzICU+JSANCiAgZmlsdGVyKHBsYXRmb3JtID09ICJTZXJpZXMiKSAlPiUgDQogIHNlbGVjdCh0aXRsZSwgcHVibGlzaGVyLCBkZXZlbG9wZXIpDQpgYGANCg0KYGBge3J9DQp3cml0ZV9jc3YobGlzdF9vZl9mcmFuY2hpc2VzLCAiY2xlYW5fZGF0YS9saXN0X29mX2ZyYW5jaGlzZXMuY3N2IikNCmBgYA0KDQojIFRoYXRzIGFsbCBpIHdhbnQgZnJvbSB0aGlzIG9uZSBpIHRoaW5rLiBNaWdodCBjb21lIGJhY2sgdG8gaXQNCg0KIyBCYWNrIHRvIFN0ZWFtDQoNCmBgYHtyfQ0Kc3RlYW0NCmBgYA0KDQpgYGB7cn0NCiMgaSBkb24ndCBrbm93IG11Y2ggYWJvdXQgdGl0bGVzIHRoYXQgYXJlbid0IGluIGVuZ2xpc2ggLSBhcmUgYWxsIHRoZSBvbmVzIGluIHRoaXMgZGF0YXNldCBlbmdsaXNoPw0KDQpzdGVhbSA8LSBzdGVhbSAlPiUgDQogIGZpbHRlcihlbmdsaXNoID09IDEpICU+JSANCiAgc2VsZWN0KCFlbmdsaXNoKQ0KDQojIGFwcHJveCAxMDAwIGFyZW4ndCAtIGJ1dCB3aG8gYXJlIHRoZXk/DQoNCnN0ZWFtICU+JSANCiAgZmlsdGVyKGVuZ2xpc2ggPT0gMCkgJT4lIA0KICBmaWx0ZXIob3duZXJzICE9ICIwLTIwMDAwIikNCg0KIyBNYWpvcml0eSBzZWVtIHRvIGJlIGNoaW5lc2UvamFwYW5lc2UvcnVzc2lhbiBpbiBvcmlnaW4uIFByb2JhYmx5IG5vdCByZWFsbHkgaW4gdGhlIHNjb3BlIG9mIGEgZHVuZGVlIGJhc2VkIGNvbXBhbnkNCiMgVGhpcyBtYWlubHkgc3VnZ2VzdHMgdGhhdCB0aGVzZSBhcmUgdGl0bGVzIHRoYXQgZG9uJ3QgaGF2ZSBzdXBwb3J0IGZvciAgbXVsdGlwbGUgbGFuZ3VhZ2VzDQojIEFsc28gZG9uJ3Qgc2VlbSB0byBoYXZlIGEgbG90IG9mIHNhbGVzLiBDYW4gcHJvYmFibHkganVzdGlmeSBqdXN0IGRyb3BwaW5nIHRoZXNlIGZvciB0aGUgdGltZSBiZWluZy4NCmBgYA0KDQojIEhZUE9USEVTSVMgVEVTVDogSU4gU1RFQU0gREFUQSwgRE9FUyBIQVZJTkcgU1VQUE9SVCBGT1IgTVVMVElQTEUgTEFOR1VBR0VTIEFGRkVDVCBTQUxFUz8gRFVOTk8gTUFURSBCVVQgSSBDQU4gQ0hFQ0sgSSBHVUVTUw0KDQpgYGB7cn0NCiMgbWFrZSBwbGF0Zm9ybXMgd2lkZXIgLSB3aW5kb3dzX3N1cHBvcnQgPSBUUlVFL0ZBTFNFLCBtYWNfc3VwcG9ydCA9IFRSVUUvRkFMU0UsIGxpbnV4X3N1cHBvcnQgPSBUUlVFL0ZBTFNFDQpzdGVhbSA8LSBzdGVhbSAlPiUgDQogICBzZXBhcmF0ZShjb2wgPSBwbGF0Zm9ybXMsIHNlcCA9ICI7IiwgaW50byA9IGMoInBsYXRmb3JtXzEiLCAicGxhdGZvcm1fMiIsICJwbGF0Zm9ybV8zIikpIA0KYGBgDQpgYGB7cn0NCiMgdGhpcyBpcyB2ZXJib3NlIGFuZCBjbHVua3ksIGJ1dCBpdCBjYXRjaGVzIGV2ZXJ5dGhpbmcgc28gd2hhdGV2ZXINCnN0ZWFtIDwtIHN0ZWFtICU+JSANCiAgbXV0YXRlKHdpbmRvd3Nfc3VwcG9ydCA9IGNhc2Vfd2hlbigNCiAgICBwbGF0Zm9ybV8xID09ICJ3aW5kb3dzIiB+IFRSVUUsDQogICAgcGxhdGZvcm1fMiA9PSAid2luZG93cyIgfiBUUlVFLA0KICAgIHBsYXRmb3JtXzMgPT0gIndpbmRvd3MiIH4gVFJVRSwNCiAgICBUUlVFIH4gRkFMU0UgKSwuYWZ0ZXIgPSBwdWJsaXNoZXIpICU+JSANCiAgbXV0YXRlKG1hY19zdXBwb3J0ID0gY2FzZV93aGVuKA0KICAgIHBsYXRmb3JtXzEgPT0gIm1hYyIgfiBUUlVFLA0KICAgIHBsYXRmb3JtXzIgPT0gIm1hYyIgfiBUUlVFLA0KICAgIHBsYXRmb3JtXzMgPT0gIm1hYyIgfiBUUlVFLA0KICAgIFRSVUUgfiBGQUxTRSksIC5hZnRlciA9IHdpbmRvd3Nfc3VwcG9ydCkgJT4lIA0KIG11dGF0ZShsaW51eF9zdXBwb3J0ID0gY2FzZV93aGVuKA0KICAgIHBsYXRmb3JtXzEgPT0gImxpbnV4IiB+IFRSVUUsDQogICAgcGxhdGZvcm1fMiA9PSAibGludXgiIH4gVFJVRSwNCiAgICBwbGF0Zm9ybV8zID09ICJsaW51eCIgfiBUUlVFLA0KICAgIFRSVUUgfiBGQUxTRSksIC5hZnRlciA9IG1hY19zdXBwb3J0KSAlPiUgDQogIHNlbGVjdCgtcGxhdGZvcm1fMSwgLXBsYXRmb3JtXzIsIC1wbGF0Zm9ybV8zKQ0KYGBgDQoNCmBgYHtyfQ0KIyBjaGFuZ2Ugb3duZXJzIHRvIGJlIG1vcmUgcmVhZGFibGUNCnN0ZWFtIDwtIHN0ZWFtICU+JSANCiAgbXV0YXRlKG93bmVycyA9IGNhc2Vfd2hlbigNCiAgICBvd25lcnMgPT0gIjAtMjAwMDAiIH4gImJlbG93IDIwayIsDQogICAgb3duZXJzID09ICIyMDAwMC01MDAwMCIgfiAiMjBrIHRvIDUwayIsDQogICAgb3duZXJzID09ICI1MDAwMC0xMDAwMDAiIH4gIjUwayB0byAxMDBrIiwNCiAgICBvd25lcnMgPT0gIjEwMDAwMC0yMDAwMDAiIH4gIjEwMGsgdG8gMjAwayIsDQogICAgb3duZXJzID09ICIyMDAwMDAtNTAwMDAwIiB+ICIyMDBrIHRvIDUwMGsiLA0KICAgIG93bmVycyA9PSAiNTAwMDAwLTEwMDAwMDAiIH4gIjUwMGsgdG8gMU0iLA0KICAgIG93bmVycyA9PSAiMTAwMDAwMC0yMDAwMDAwIiB+ICIxTSB0byAyTSIsDQogICAgb3duZXJzID09ICIyMDAwMDAwLTUwMDAwMDAiIH4gIjJNIHRvIDVNIiwNCiAgICBvd25lcnMgPT0gIjUwMDAwMDAtMTAwMDAwMDAiIH4gIjVNIHRvIDEwTSIsDQogICAgb3duZXJzID09ICIxMDAwMDAwMC0yMDAwMDAwMCIgfiAiMTBNIHRvIDIwTSIsDQogICAgb3duZXJzID09ICIyMDAwMDAwMC01MDAwMDAwMCIgfiAiMjBNIHRvIDUwTSIsDQogICAgb3duZXJzID09ICI1MDAwMDAwMC0xMDAwMDAwMDAiIH4gIjUwTSB0byAxMDBNIiwNCiAgICBvd25lcnMgPT0gIjEwMDAwMDAwMC0yMDAwMDAwMDAiIH4gIjEwME0gdG8gMjAwTSINCiAgKSkgDQoNCiAgDQoNCmBgYA0KDQpgYGB7cn0NCnN0ZWFtICU+JSANCiAgZmlsdGVyKG93bmVycyA9PSAiMTBNIHRvIDIwTSIpDQpgYGANCg0KIyMgSU4gVEhJUyBWRVJTSU9OIE9GIFRIRSBTVEVBTSBEQVRBU0VULCBhbGwgYnV0IG9mIHRoZSBnYW1lcyBsaXN0ZWQgYXMgaGF2aW5nIG1vcmUgdGhhbiAyMCBtaWxsaW9uIHBsYXllcnMgYXJlIGZyZWVtaXVtPyBmcmVlIHRvIHN0YXJ0PyBmcmVlIHRvIHBsYXk/IChwaWNrIHlvdXIgcG9pc29uKSBnYW1lcw0KDQpgYGB7cn0NCiMgQURESU5HIEEgQ09MVU1OIFRPIEZJTkQgVEhFIEZSRUUgVE8gUExBWSBTVFVGRg0KDQpzdGVhbSA8LSBzdGVhbSAlPiUgDQogIG11dGF0ZShmcmVlX3RvX3BsYXkgPSBjYXNlX3doZW4oDQogICAgc3RyX2RldGVjdChnZW5yZXMsICJGcmVlIHRvIFBsYXkiKSB+IFRSVUUsDQogICAgc3RyX2RldGVjdChzdGVhbXNweV90YWdzLCJGcmVlIHRvIFBsYXkiKSB+IFRSVUUsDQogICAgcHJpY2UgPT0gMC4wMCB+IFRSVUUsDQogICAgVFJVRSB+IEZBTFNFDQogICksLmFmdGVyID0gcHVibGlzaGVyKSANCmBgYA0KDQpgYGB7cn0NCnN0ZWFtICU+JSANCiAgZmlsdGVyKGZyZWVfdG9fcGxheSA9PSBUUlVFKQ0KYGBgDQoNCg0KDQpgYGB7cn0NCiMgQ29sdW1uIHRvIGZpbmQgYWxsIHRoZSBWUiBzdHVmZg0Kc3RlYW0gPC0gc3RlYW0gJT4lIA0KICBtdXRhdGUodmlydHVhbF9yZWFsaXR5X3N1cHBvcnQgPSBjYXNlX3doZW4oDQogICAgc3RyX2RldGVjdChuYW1lLCAiVlIiKSB+IFRSVUUsDQogICAgc3RyX2RldGVjdChzdGVhbXNweV90YWdzLCJWUiIpIH4gVFJVRSwNCiAgICBUUlVFIH4gRkFMU0UNCiAgKSwuYWZ0ZXIgPSBmcmVlX3RvX3BsYXkpDQpgYGANCg0KYGBge3J9DQpzdGVhbSAlPiUgDQogIGZpbHRlcih2aXJ0dWFsX3JlYWxpdHlfc3VwcG9ydCA9PSBUUlVFKQ0KYGBgDQpgYGB7cn0NCiMgTm93IHRoZSBzYW1lIGJ1dCBmb3IgbXVsdGktcGxheWVyIC0gTG9jYWwgYW5kIE9ubGluZSANCnN0ZWFtIDwtIHN0ZWFtICU+JSANCiAgbXV0YXRlKG11bHRpcGxheWVyID0gY2FzZV93aGVuKA0KICAgIHN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIkxvY2FsIE11bHRpLVBsYXllciIpICYgc3RyX2RldGVjdChjYXRlZ29yaWVzLCAiT25saW5lIE11bHRpLVBsYXllciIpICYgc3RyX2RldGVjdChjYXRlZ29yaWVzLCAiQ28tb3AiKSB+ICJMb2NhbCArIE9ubGluZSArIENvLW9wIiwNCiAgICBzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJMb2NhbCBNdWx0aS1QbGF5ZXIiKSAmIHN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIk9ubGluZSBNdWx0aS1QbGF5ZXIiKSAmICFzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJDby1vcCIpIH4gIkxvY2FsICsgT25saW5lIiwNCiAgICBzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJMb2NhbCBNdWx0aS1QbGF5ZXIiKSAmIHN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIkNvLW9wIikgJiAhc3RyX2RldGVjdChjYXRlZ29yaWVzLCAiT25saW5lIE11bHRpLVBsYXllciIpIH4gIkxvY2FsICsgQ28tb3AiLA0KICAgIHN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIk9ubGluZSBNdWx0aS1QbGF5ZXIiKSAmIHN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIkNvLW9wIikgJiAhc3RyX2RldGVjdChjYXRlZ29yaWVzLCAiTG9jYWwgTXVsdGktUGxheWVyIikgfiAiT25saW5lICsgQ28tb3AiLA0KICAgIHN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIkxvY2FsIE11bHRpLVBsYXllciIpICYgIXN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIk9ubGluZSBNdWx0aS1QbGF5ZXIiKSB+ICJMb2NhbCBvbmx5IiwNCiAgICBzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJPbmxpbmUgTXVsdGktUGxheWVyIikgJiAhc3RyX2RldGVjdChjYXRlZ29yaWVzLCAiTG9jYWwgTXVsdGktUGxheWVyIikgfiAiT25saW5lIG9ubHkiLA0KICAgIHN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIk11bHRpLXBsYXllciIpICYgc3RyX2RldGVjdChjYXRlZ29yaWVzLCAiTU1PIikgfCBzdHJfZGV0ZWN0KHN0ZWFtc3B5X3RhZ3MsICJNTU8iKSB+ICJPbmxpbmUgb25seSIsDQogICAgc3RyX2RldGVjdChjYXRlZ29yaWVzLCAiQ28tb3AiKSB+ICJDby1vcCBtdWx0aXBsYXllciIsDQogICAgc3RyX2RldGVjdChjYXRlZ29yaWVzLCAiTXVsdGktcGxheWVyIikgJiAhc3RyX2RldGVjdChjYXRlZ29yaWVzLCAiT25saW5lIE11bHRpLVBsYXllciIpICYgIXN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIkxvY2FsIE11bHRpLVBsYXllciIpIH4gIk11bHRpcGxheWVyIChVbnNwZWNpZmllZCkiLA0KICAgIFRSVUUgfiAiTm8gbXVsdGlwbGF5ZXIiDQogICksLmFmdGVyID0gdmlydHVhbF9yZWFsaXR5X3N1cHBvcnQpIA0KDQojIEkgdGhpbmsgaSBjb3VsZCBhZGQgbW9yZSBsZXZlbHMsIGJ1dCBub3QgcmlnaHQgbm93DQojIHJlbWVtYmVyIGV4YWN0bHkgb25lIGNvbW1lbnQgYWdvIHdoZW4gaSBzYWlkICJsb2NhbCBhbmQgb25saW5lIiANCmBgYA0KDQpgYGB7cn0NCnN0ZWFtX2NoZWNrcG9pbnQgPC0gc3RlYW0NCmBgYA0KDQojIFByZXR0eSBoYXBweSB3aXRoIHN0ZWFtIGRhdGEgLSBDb3VsZCBhZGp1c3QgYXZlcmFnZS9tZWRpYW4gcGxheXRpbWUgdG8gYmUgaG91cnMgKGFzc3VtaW5nIHRoYXQgdGhleSBhcmUgY3VycmVudGx5IHJlY29yZGVkIGluIG1pbnV0ZXMpLCBvciBhZGRpbmcgYSByYXRpbyANCiAgdG8gcmF0aW5ncyAoc2ltaWxpYXIgdG8gaG93IHN0ZWFtIGRvZXMgaXQpDQpfX19fX19fX19fDQogIA0KICBDSEVDS1BPSU5UDQpEb24ndCBib3RoZXIgcnVubmluZyBhbnl0aGluZyBhYm92ZSB0aGlzIC0gYW55dGhpbmcgd29ydGggc2F2aW5nIGhhcyBiZWVuIHNhdmVkIGluIA0KICANCmBgYHtyfQ0Kd3JpdGVfY3N2KHN0ZWFtX2NoZWNrcG9pbnQsICJyYXdfZGF0YS9zdGVhbV9jaGVja3BvaW50LmNzdiIpDQpgYGANCg0KX19fX19fX19fDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGphbml0b3IpDQoNCnN0ZWFtIDwtIHJlYWRfY3N2KCJyYXdfZGF0YS9zdGVhbV9jaGVja3BvaW50LmNzdiIpDQpgYGANCg0K